/* global RESOURCE_ENERGY */

import Role from 'role/role';
import {getResourcesIn} from 'utils/store';

declare global {
	interface HelperCreep extends Creep {
		role: 'helper';
		memory: HelperCreepMemory;
		heapMemory: HelperCreepHeapMemory;
	}

	interface HelperCreepMemory extends CreepMemory {
		role: 'helper';
		delivering?: boolean;
	}

	interface HelperCreepHeapMemory extends CreepHeapMemory {
	}
}

export default class HelperRole extends Role {
	/**
	 * Makes a creep behave like a helper.
	 *
	 * @param {Creep} creep
	 *   The creep to run logic for.
	 */
	run(creep: HelperCreep) {
		if (!creep.room.boostManager) {
			this.parkHelper(creep);
			return;
		}

		if (creep.memory.delivering && creep.store.getUsedCapacity() === 0) {
			this.setHelperState(creep, false);
		}
		else if (!creep.memory.delivering && creep.store.getFreeCapacity() === 0) {
			this.setHelperState(creep, true);
		}

		if (this.performHelperCleanup(creep)) {
			return;
		}

		if (creep.memory.delivering) {
			this.performHelperDeliver(creep);
			return;
		}

		this.performHelperGather(creep);
	}

	/**
	 * Moves a helper creep to its designated parking spot.
	 *
	 * @param {Creep} creep
	 *   The creep to run logic for.
	 */
	parkHelper(creep: HelperCreep) {
		if (!creep.room.roomPlanner) return;

		const targetPos = _.sample(creep.room.roomPlanner.getLocations('helper_parking'));
		if (!targetPos) return;

		creep.whenInRange(1, targetPos, () => {
			// Wait around helpfully!
		});
	}

	/**
	 * Puts this creep into or out of deliver mode.
	 *
	 * @param {Creep} creep
	 *   The creep to run logic for.
	 * @param {boolean} delivering
	 *   Whether this creep should be delivering resources.
	 */
	setHelperState(creep: HelperCreep, delivering: boolean) {
		creep.memory.delivering = delivering;
	}

	/**
	 * Checks if any of the labs have the wrong mineral type assigned, and clears those out.
	 *
	 * @param {Creep} creep
	 *   The creep to run logic for.
	 *
	 * @return {boolean}
	 *   True if the creep is busy cleaning up resources.
	 */
	performHelperCleanup(creep: HelperCreep): boolean {
		const storage = creep.room.storage;
		const terminal = creep.room.terminal;

		const boostManager = creep.room.boostManager;
		const boostLabs = boostManager.getBoostLabs();
		for (const lab of boostLabs) {
			if (lab.mineralType && lab.mineralType !== boostManager.getRequiredBoostType(lab.id)) {
				if (creep.memory.delivering) {
					// Put everything away.
					let target: AnyStoreStructure = terminal;
					if (storage && storage.store.getUsedCapacity() + creep.store.getUsedCapacity() < storage.store.getCapacity()) {
						target = storage;
					}

					creep.whenInRange(1, target, () => {
						creep.transferAny(target);
					});
				}
				// Clean out lab.
				else {
					creep.whenInRange(1, lab, () => {
						creep.withdraw(lab, lab.mineralType);
					});
				}

				return true;
			}
		}

		return false;
	}

	/**
	 * Makes a helper creep deliver its resources.
	 *
	 * @param {Creep} creep
	 *   The creep to run logic for.
	 */
	performHelperDeliver(creep: HelperCreep) {
		const storage = creep.room.storage;
		const terminal = creep.room.terminal;

		if (this.performHelperLabDeliver(creep)) return;

		// Store anything else in storage or terminal.
		const resourceType = getResourcesIn(creep.store)[0];
		const target = creep.room.getBestStorageTarget(creep.store[resourceType], resourceType);

		creep.whenInRange(1, target, () => {
			creep.transfer(target, resourceType);
		});
	}

	/**
	 * Makes a helper creep deliver its resources to labs according to orders.
	 *
	 * @param {Creep} creep
	 *   The creep to run logic for.
	 *
	 * @return {boolean}
	 *   Whether a delivery is taking place.
	 */
	performHelperLabDeliver(creep: HelperCreep): boolean {
		const boostManager = creep.room.boostManager;
		const boostLabs = boostManager.getBoostLabs();
		for (const lab of boostLabs) {
			const resourceType = boostManager.getRequiredBoostType(lab.id);

			if (creep.store.getUsedCapacity(resourceType) > 0) {
				const diff = boostManager.getRequiredBoostAmount(lab.id) - lab.store.getUsedCapacity(resourceType);
				const amount = Math.min(diff, creep.store[resourceType], lab.store.getFreeCapacity(resourceType));
				if (amount > 0) {
					creep.whenInRange(1, lab, () => {
						creep.transfer(lab, resourceType, amount);
					});

					return true;
				}
			}

			if (creep.store.getUsedCapacity(RESOURCE_ENERGY) > 0) {
				const diff = boostManager.getRequiredEnergyAmount(lab.id) - lab.store.getUsedCapacity(RESOURCE_ENERGY);
				const amount = Math.min(diff, creep.store[RESOURCE_ENERGY], lab.store.getFreeCapacity(RESOURCE_ENERGY));
				if (amount > 0) {
					creep.whenInRange(1, lab, () => {
						creep.transfer(lab, RESOURCE_ENERGY, amount);
					});

					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Makes a helper creep gather resources.
	 *
	 * @param {Creep} creep
	 *   The creep to run logic for.
	 */
	performHelperGather(creep: HelperCreep) {
		const storage = creep.room.storage;
		const terminal = creep.room.terminal;

		if (this.performHelperLabGather(creep)) return;

		// Get energy to fill labs when needed.
		const boostManager = creep.room.boostManager;
		const labs = boostManager.getBoostLabs();
		const totalNeededEnergy = _.sum(labs, lab => Math.min(lab.store.getFreeCapacity(RESOURCE_ENERGY), boostManager.getRequiredEnergyAmount(lab.id) - lab.store.getUsedCapacity(RESOURCE_ENERGY)));
		const target = creep.room.getBestStorageSource(RESOURCE_ENERGY);
		if (target) {
			const amount = Math.min(totalNeededEnergy, creep.store.getFreeCapacity(RESOURCE_ENERGY), target.store.getUsedCapacity(RESOURCE_ENERGY));
			if (amount > 0) {
				creep.whenInRange(1, target, () => {
					creep.withdraw(target, RESOURCE_ENERGY, amount);
				});

				return;
			}
		}

		// If we got here, there's nothing left to gather. Deliver what we have stored.
		if (creep.store.getUsedCapacity() > 0) {
			this.setHelperState(creep, true);

			return;
		}

		// Failing that, just go into parking position.
		this.parkHelper(creep);
	}

	/**
	 * Makes a helper creep gather resources needed for lab orders.
	 *
	 * @param {Creep} creep
	 *   The creep to run logic for.
	 *
	 * @return {boolean}
	 *   Whether the creep is currently fulfilling an order.
	 */
	performHelperLabGather(creep: HelperCreep): boolean {
		const boostManager = creep.room.boostManager;
		const boostLabs = boostManager.getBoostLabs();
		for (const lab of boostLabs) {
			const resourceType = boostManager.getRequiredBoostType(lab.id);

			const diff = boostManager.getRequiredBoostAmount(lab.id) - creep.store.getUsedCapacity(resourceType) - lab.store.getUsedCapacity(resourceType);
			if (diff <= 0) continue;

			const target = creep.room.getBestStorageSource(resourceType);
			if (!target) continue;
			const amount = Math.min(diff, creep.store.getFreeCapacity(resourceType), target.store.getUsedCapacity(resourceType), lab.store.getFreeCapacity(resourceType) - creep.store.getUsedCapacity(resourceType));
			if (amount > 0) {
				creep.whenInRange(1, target, () => {
					creep.withdraw(target, resourceType, amount);
				});

				return true;
			}
		}

		return false;
	}
}
